﻿/*
  Version 0.1
*/
{
#Singleinstance, Force
#NoEnv
#Include %A_Scriptdir%\Lib\Class_EasyINI.ahk
#Include %A_ScriptDir%\Lib\Gdip.ahk
#Include %A_Scriptdir%\User\
SendMode Input
SetWorkingDir %A_ScriptDir%
; 设置图标
Menu,Tray,Icon,%A_ScriptDir%\GestureZ.ico,1
}
; 初始化
{
		Ini_Config := Class_EasyINI(A_Scriptdir "\User\Config.ini")
    If not strlen(Ini_Config.Config.Count)
    {
        Ini_Config.Config["Count"] := 0
        Ini_Config.Save()
    }
    If not strlen(Ini_Config.Config.ActionDo)
    {
        Ini_Config.Config["ActionDo"] := 0
        Ini_Config.Save()
    }
		Ini_GesBase := Class_EasyINI(A_Scriptdir "\User\GestureBasic.ini")
		If not fileexist(A_Scriptdir "\User\GestureUser.ini")
		{
				FileAppend, , %A_Scriptdir%\User\GestureUser.ini
				Ini_GesUser := Class_EasyINI(A_Scriptdir "\User\GestureUser.ini")
				Ini_GesUser.AddSection("Gesture")
				Ini_GesUser.Save()
		}
		Else
				Ini_GesUser := Class_EasyINI(A_Scriptdir "\User\GestureUser.ini")
    LoadHotkey()
    ;Menu, Tray, NoStandard
    ;Menu, Tray, Add, 准确率, Null
    ;Menu, Tray, Disable, 准确率
}
return
#Include %A_ScriptDir%\User\Action.ahk
Null:
return

; MG_Recognize() {{{2
; 手势判断
MG_Recognize() {
	; Modify from [module] Mouse gestures
	; http://www.autohotkey.com/board/topic/52201-module-mouse-gestures/
	; Thanks Learning one
	Critical
  Global Ini_Config, LastGesture, LastGesturePos
  LastGesture := ""
	LastGesturePos := []
	LastGesturePos[0] := 0
	MGHotkey := RegExReplace(A_ThisHotkey,"^(\w* & |\W*)")
	CoordMode, mouse, Screen
	MouseGetPos, StartX , StartY, gz_win_id, gz_win_control
	WinGetClass,gz_win_class,ahk_id %gz_win_id%
	winGet,gz_win_file,ProcessName,ahk_id %gz_win_id%
	ControlGetFocus, gz_win_control ,ahk_id %gz_win_id% 
  GZObj := {}
	GZObj["id"] := gz_win_id
	GZObj["control"] := gz_win_control
	GZObj["class"] := gz_win_class
	GZObj["x"] := startX
	GZObj["y"] := startY

	If Ini_Config.config.draw
	{
		If !pToken := Gdip_Startup()
			return
		Width := A_ScreenWidth , Height := A_ScreenHeight
		Gui, 1: -Caption +E0x80000 +LastFound +OwnDialogs +Owner +AlwaysOnTop
		Gui, 1: Show, NA
		hwnd1 := WinExist()
		hbm := CreateDIBSection(Width, Height)
		hdc := CreateCompatibleDC()
		obm := SelectObject(hdc, hbm)
		G := Gdip_GraphicsFromHDC(hdc)
		Gdip_SetSmoothingMode(G, 4)
		pPen := Gdip_CreatePen(ARGB_FromRGB(0xAA,Ini_Config.config.color),3)
	}
	Loop
	{
		if !(GetKeyState(MGHotkey, "p")) or (Timeout > Ini_Config.Config.timeout) {
			;If FileExist(A_ScriptDir "\GestureZ.exe")
			;Menu,Tray,Icon,%A_ScriptDir%\GestureZ.exe,1
			If pToken And Ini_Config.config.draw
			{
				Gdip_DeletePen(pPen)
				SelectObject(hdc, obm)
				DeleteObject(hbm)
				DeleteDC(hdc)
				Gdip_DeleteGraphics(G)
				Gdip_Shutdown(pToken)
				GUI,1:Destroy
				WinActive("ahk_id " gz_win_id)
			}
			If (TimeOut > Ini_Config.Config.timeout)
      {
				x1 := GZObj["x"]
				y1 := GZObj["y"]
				x2 := xx
				y2 := yy
				;MouseClickDrag,L,%x1%,%y1%,%x2%,%y2%
				key := SubStr(MGHotkey,1,1)
				SendEvent {click %x1%,%y1%,%key%,down}
				MouseMove ,%x2% ,%y2%,0
				Loop
				{
					if !(GetKeyState(MGHotkey, "p"))
					{
						SendEvent {click,%x2%,%y2%,%key%,up}
						return
					}
				}
			}
			Else If not RegExMatch(LastGesture,"\d"){
				SendInput, {%MGHotkey%}
				return
			}
			Ini_Config.Config.Count++
			If ( ges := GZ_ReadName(LastGesture))
      {
					GZ_ActionDo(ges, GZObj)
          GZ_ActionDoCount++
          Ini_Config.Config.ActionDo++
      }
			Else
			{
					If Ini_Config.config.autolearn
					    GestureLearn(LastGesture)
			}
      c := Ini_Config.Config.Count
      a := Ini_Config.Config.ActionDo
			;Tooltip, % "识别为: " ges "`n准确率: " Round(a/c*100, 2) "%"
      MG_ReadName2()
      Ini_Config.Save() 
			Return LastGesture
		}
		EndX_Old := EndX
		EndY_Old := EndY
		MouseGetPos, xx, yy
		If (xx == EndX) AND ( yy == EndY)
		{
				timeout := A_TickCount - Timestamp
				sleep,20
				continue
		}
		Else
			timeout := 0
		Timestamp := A_TickCount
		EndX := xx
		EndY := yy
		Radius := MG_GetRadius(StartX, StartY, EndX, EndY)
		MoveRadius := MG_GetRadius(EndX_Old, EndY_Old, EndX, EndY) 
		;Tooltip % Radius
		; 画笔
		If ( MoveRadius >= 1 ) And  Ini_Config.config.draw
		{
			Gdip_DrawLine(G, pPen, EndX_Old, EndY_Old, EndX , EndY)
			MG_Review_Date(EndX_Old , EndY_Old, EndX, EndY)
		}
		If ( MoveRadius >= 3 )
		{
			If not init 
			{
				;If FileExist(A_ScriptDir "\GestureZ.exe")
				;Menu,Tray,Icon,%A_ScriptDir%\GestureZ.exe,7
				init := true
			}
      if Ini_Config.config.draw
			UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)
		}
	
		; 当移动半径超过20象素才进行角度判断
		If (Radius < 20)
		    Continue
		Angle := MG_GetAngle(StartX, StartY, EndX, EndY)
		MouseGetPos, StartX, StartY

		CurMove := MG_GetMove(Angle)
		if !(CurMove = LastMove)
		{
			LastGesture .= CurMove
			if Ini_Config.config.tooltip
				Tooltip % MG_Say(LastGesture)
			LastMove := CurMove
		}
	}
	Critical,off
}

; GZ_ReadName(ges) {{{2
; 获取手势对应的手势名
GZ_ReadName(ges)
{
  Global Ini_GesBase, Ini_GesUser
  if not strlen(g := Ini_GesUser.Gesture[ges])
    g := Ini_GesBase.Gesture[ges]
	return g
}
; MG_Say(Gestures) {{{2
; 转换为手势名，易于直观显示
MG_Say(Gestures){
	Gestures := RegExReplace(Gestures,"1","↑")
	Gestures := RegExReplace(Gestures,"2","↗")
	Gestures := RegExReplace(Gestures,"3","→")
	Gestures := RegExReplace(Gestures,"4","↘")
	Gestures := RegExReplace(Gestures,"5","↓")
	Gestures := RegExReplace(Gestures,"6","↙")
	Gestures := RegExReplace(Gestures,"7","←")
	Return Gestures := RegExReplace(Gestures,"8","↖")
}

; MG_GetMove(Angle) {{{2
; 根据角度获取方向
MG_GetMove(Angle) {
	If ( Angle > 337.5 ) OR ( Angle <= 22.5)
		return 1
	If ( Angle > 22.5 ) And ( Angle <= 67.5)
		return 2
	If ( Angle > 67.5 ) And ( Angle <= 112.5)
		return 3
	If ( Angle > 112.5 ) And ( Angle <= 157.5)
		return 4
	If ( Angle > 157.5 ) And ( Angle <= 202.5)
		return 5
	If ( Angle > 202.5 ) And ( Angle <= 247.5)
		return 6
	If ( Angle > 247.5 ) And ( Angle <= 292.5)
		return 7
	If ( Angle > 292.5 ) And ( Angle <= 337.5)
		return 8
}

; MG_GetAngle(StartX, StartY, EndX, EndY) {{{2
; 获取角度
MG_GetAngle(StartX, StartY, EndX, EndY) {
    x := EndX-StartX, y := EndY-StartY
    if x = 0
    {
        if y > 0
        return 180
        Else if y < 0
        return 360
        Else
        return
    }
    deg := ATan(y/x)*57.295779513
    if x > 0
    return deg + 90
    Else
    return deg + 270	
}
; MG_GetRadius(StartX, StartY, EndX, EndY) {{{2
; 获取半径
MG_GetRadius(StartX, StartY, EndX, EndY) {
	a := Abs(endX-startX), b := Abs(endY-startY), Radius := Sqrt(a*a+b*b)
    Return Radius    
}
; MG_Review_Date(x1,y1,x2,y2) {{{2
; 保存手势路径数据
MG_Review_Date(x1,y1,x2,y2){
	Global LastGesturePos
	Pos := []
	Pos.x1 := x1
	Pos.y1 := y1
	Pos.x2 := x2
	Pos.y2 := y2
	LastGesturePos[LastGesturePos[0]+1] := Pos
	LastGesturePos[0] += 1
}
; MG_Review() {{{2
; 重绘出最后一次的手势
MG_Review(){
	Global LastGesturePos,ReviewLock
	ReviewLock := True
	critical
	If !pToken := Gdip_Startup()
		return
	xmin := A_ScreenWidth
	ymin := A_ScreenHeight
	xmax := 0	
	ymax := 0	
	Loop,% LastGesturePos[0]
	{
		pos := LastGesturePos[A_Index]
		If Pos.x1 > xmax
			xmax := Pos.x1
		If Pos.x2 > xmax
			xmax := Pos.x2
		If Pos.x1 < xmin
			xmin := Pos.x1
		If Pos.x2 < xmin
			xmin := Pos.x2
		If Pos.y1 > ymax
			ymax := Pos.y1
		If Pos.y2 > ymax
			ymax := Pos.y2
		If Pos.y1 < ymin
			ymin := Pos.y1
		If Pos.y2 < ymin
			ymin := Pos.y2
	}
	Width := A_ScreenWidth , Height := A_ScreenHeight
	Gui, review: -Caption +E0x80000 +LastFound +OwnDialogs +Owner +AlwaysOnTop
	Gui, review: Show, NA
	hwnd1 := WinExist()
	hbm := CreateDIBSection(Width, Height)
	hdc := CreateCompatibleDC()
	obm := SelectObject(hdc, hbm)
	G := Gdip_GraphicsFromHDC(hdc)
	Gdip_SetSmoothingMode(G, 4)
	pBrush := Gdip_BrushCreateSolid(0xff112211)
	Gdip_FillRectangle(G, pBrush, xmin-12, ymin-12, xmax-xmin+24, ymax-ymin+24)
	pBrushWhite := Gdip_BrushCreateSolid(0xffffffff)
	Gdip_FillRectangle(G, pBrushWhite, xmin-10, ymin-10, xmax-xmin+20, ymax-ymin+20)
	UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)
	pPen := Gdip_CreatePen(0xffff0000,3)
	Loop,% LastGesturePos[0]
	{
		Sleep,50
		pos := LastGesturePos[A_Index]
		Gdip_DrawLine(G, pPen,Pos.x1, Pos.y1, Pos.x2, Pos.y2)
		UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)
	}
	Sleep,1400
	Gdip_DeleteBrush(pBrush)
	Gdip_DeleteBrush(pBrushWhite)
	Gdip_DeletePen(pPen)
	SelectObject(hdc, obm)
	DeleteObject(hbm)
	DeleteDC(hdc)
	Gdip_DeleteGraphics(G)
	Gdip_Shutdown(pToken)
	MG_Review_SaveImg()
	critical,off
	ReviewLock := False
	GUI,review:Destroy
}
reviewGuiEscape:
	If not ReviewLock
	GUI,review:Destroy
return
; MG_Review_SaveImg() {{{2
MG_Review_SaveImg(){
	Critical
	pFile := A_Temp "\GestureZ.png"
	pFileHtml := A_Temp "\GestureZ.html"
	Global LastGesturePos
	If !pToken := Gdip_Startup()
		return
	xmin := A_ScreenWidth
	ymin := A_ScreenHeight
	xmax := 0	
	ymax := 0	
	Loop,% LastGesturePos[0]
	{
		pos := LastGesturePos[A_Index]
		If Pos.x1 > xmax
			xmax := Pos.x1
		If Pos.x2 > xmax
			xmax := Pos.x2
		If Pos.x1 < xmin
			xmin := Pos.x1
		If Pos.x2 < xmin
			xmin := Pos.x2
		If Pos.y1 > ymax
			ymax := Pos.y1
		If Pos.y2 > ymax
			ymax := Pos.y2
		If Pos.y1 < ymin
			ymin := Pos.y1
		If Pos.y2 < ymin
			ymin := Pos.y2
	}
	pBitmap := Gdip_CreateBitmap(w:= xmax-xmin+20,h:=ymax-ymin+20)
	xmin -= 10
	ymin -= 10
	G2 := Gdip_GraphicsFromImage(pBitmap)
	Gdip_SetSmoothingMode(G2, 4)
	pBrushWhite := Gdip_BrushCreateSolid(0xffffffff)
	Gdip_FillRectangle(G2, pBrushWhite, 0, 0, w, h)
	Gdip_DeleteBrush(pBrushWhite)
	pPen := Gdip_CreatePen(0xffff0000,3)
	Loop,% LastGesturePos[0]
	{
		pos := LastGesturePos[A_Index]
		Gdip_DrawLine(G2, pPen,Pos.x1-xmin, Pos.y1-ymin, Pos.x2-xmin, Pos.y2-ymin)
		UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)
	}
	Gdip_DeletePen(pPen)
	Gdip_SaveBitmapToFile(pBitmap, pfile)
	Gdip_DisposeImage(pBitmap)
	Gdip_DeleteGraphics(G2)
	Gdip_Shutdown(pToken)
	If FileExist(pFileHtml)
		FileDelete,%pFileHtml%
FileAppend,
(
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
 <head>
  <title> GestureZ </title>
  <style type="text/css">
    .test{
        margin:0 auto;
        display:block;
        }
  </style>
 </head>
 <body>
<img class="test" src="%pFile%" width=200 height=200/>
 </body>
</html> 
),%pFileHtml%
	Critical,off
	return pFileHtml
}
 
; MG_Review_Load(name) {{{2
MG_Review_Load(name){
	Global LastGesturePos
	INIRead,load,%A_SCRIPTDIR%\User\GestureView.ini,%name%
	Loop,Parse,Load,`n,`r
	{
		If not Strlen(A_LoopField)
			continue
		Pos := []
		PosData := RegExReplace(A_LoopField,"^\d*=")
		Loop,Parse,PosData,`,
		{
			If A_Index = 1
				Pos.x1 := A_LoopField
			If A_Index = 2
				Pos.y1 := A_LoopField
			If A_Index = 3
				Pos.x2 := A_LoopField
			If A_Index = 4
				Pos.y2 := A_LoopField
		}
		idx := RegExReplace(A_LoopField,"=.*$")
		LastGesturePos[idx] := Pos
	}
	LastGesturePos[0] := idx
}
; MG_Review_Save(name) {{{2
; 保存数据到INI
MG_Review_Save(name){
	Global LastGesturePos
  If not fileexist(A_Scriptdir "\User\GestureView.ini")
      FileAppend, , %A_Scriptdir%\User\GestureView.ini
	INIdelete,%A_ScriptDir%\User\GestureView.ini,%name%
	Loop,% LastGesturePos[0]
	{
		pos := LastGesturePos[A_Index]
		save := Pos.x1 "," Pos.y1 "," Pos.x2 "," Pos.y2
		INIWrite,%save%,%A_ScriptDir%\User\GestureView.ini,%name%,%A_Index%
	}
}
; GZ_WriteName(ges,name) {{{2
; 写入手势对应的手势名到识别库中
GZ_WriteName(ges,name){
	Global Ini_GesUser
  Ini_GesUser.Gesture[ges] := Name
  Ini_GesUser.Save()
	;gesINI.INIWrite("Gesture",ges,Name)
}
   
; GestureLearn(ges) {{{2
; 手势学习界面 
GestureLearn(ges,load=true){
	Global Ini_GesUser, Ini_Config, LastGesture, WB
	GUI,gesLearn:Destroy
	GUI,gesLearn:Default
ges=
(
Up
Down
Left
Right
/Down
/Up
\Down
\Up
Up-left
Up-right
Down-left
Down-Right
Right-down
Right-up
Left-down
Left-up
1
2
3
4
5
7
8
9
<
>
?
A
B
C
D
E
F
G
H
J
M
N
O
P
Q
R
S
T
U
V
W
Y
Z
)
    GUI,gesLearn:+Delimiter`n
    GUI,gesLearn:Default
		Gui,gesLearn:Add,ActiveX,x10 y10 w250 h250 vWB border -VScroll, Shell.Explorer
	  WB.Navigate(MG_Review_SaveImg())
		Gui,gesLearn:Add,Text,x290 y30 w200 h80 ,手势名(&M)
		Gui,gesLearn:Add,GroupBox,x280 y80 w200 h80
		Gui,gesLearn:Add,Text,x290 y100 w80 h18 ,手势名(&M)
		Gui,gesLearn:Add,ComboBox,x290 y120 w176, % ges
		Gui,gesLearn:Add,Edit,x280 y10 w200 readonly r4, % MG_Say(LastGesture)
		Gui,gesLearn:Add,Button,x280 y180 w90 h30 gButton_MG_Review,回放(&R)
		Gui,gesLearn:Add,Button,x280 y220 w90 h30 gButton_MG_Save,保存画法(&L)
		Gui,gesLearn:Add,Button,x390 y180 w90 h30 gButton_MG_Learn Default,学习(&L)
		Gui,gesLearn:Add,Button,x390 y220 w90 h30 gButton_Destroy,取消(&C)
		Gui,gesLearn:Show,x495 y256 w490 h270, GestureZ 学习
    GuiControl, Text, Edit1, % Ini_Config.Config.DefaultGes
    return
}
Button_Destroy:
	GUI,gesLearn:Destroy
return
Button_MG_Learn:
	GUI,gesLearn:Default
	GUIControlGet,name,,Edit1
	If RegExMatch(name,"^[\s\t]*$")
  {
    Msgbox 请选择或输入手势名!
		return
  }
	StringLower, name, name,T
	GZ_WriteName(LastGesture,Name)
	GUI,gesLearn:Destroy
return
Button_MG_Review:
	MG_Review()
return
Button_MG_Save:
	GUI,gesLearn:Default
	GUIControlGet,name,,Edit1
	If RegExMatch(name,"^[\s\t]*$")
  {
    Msgbox 请选择或输入手势名!
		return
  }
	StringLower, name, name,T
	MG_Review_Save(name)
return
Button_MG_Load:
	GUI,gesLearn:Default
	GUIControlGet,name,,Edit1
  If RegExMatch(name,"^[\s\t]*$")
  {
    Msgbox 请选择或输入手势名!
		return
  }
	StringLower, name, name, T
	MG_Review_Load(name)
	MG_Review()
return
; ToMatch(str) {{{2
; 正则表达式转义
ToMatch(str)
{
	str := RegExReplace(str,"\+|\?|\.|\*|\{|\}|\(|\)|\||\^|\$|\[|\]|\\","\$0")
	Return RegExReplace(str,"\s","\s")
}

; GetStringLen(string) {{{2
GetStringLen(string)
{
	;[^\x00-\xff]
	count := 0
	Loop,Parse,String
		If RegExMatch(A_LoopField,"[^\x00-\xff]")
			Count += 2
		Else
			Count++
	return Count
}

; EmptyMem(PID="AHK Rocks") {{{2
EmptyMem(PID="AHK Rocks")
{
    pid:=(pid="AHK Rocks") ? DllCall("GetCurrentProcessId") : pid
    h:=DllCall("OpenProcess", "UInt", 0x001F0FFF, "Int", 0, "Int", pid)
    DllCall("SetProcessWorkingSetSize", "UInt", h, "Int", -1, "Int", -1)
    DllCall("CloseHandle", "Int", h)
}

/********************************************
* ARGB_GET(ARGB, item)
*
* ARGB: ARGB value
*
* item: A,R,G,B -> field you want
*
*********************************************
*/
ARGB_GET(ARGB, item){

    if(item = "A"){
        return (ARGB >> 24) & 0xFF
    }else if ( item = "R"){
        return (ARGB >> 16) & 0xFF
    }else if (item = "G"){
        return (ARGB >> 8) & 0xFF
    }else if (item = "B"){
        return (ARGB) & 0xFF
    }
}


/********************************************
* ARGB(A,R,G,B)
*
* returns the ARGB value
*
*********************************************
*/
ARGB(A,R,G,B){
    ;---- ensure that the values are max 0xFF (255)
    A := A & 0xFF, R := R & 0xFF
    G := G & 0xFF, B := B & 0xFF
    ;---------------------------------------------
    return ((((((R << 16) | (G << 8)) | B) | (A << 24))) & 0xFFFFFFFF)
}

/********************************************
* ARGB(A,RGB)
*
* returns the ARGB value from RGB
*
*********************************************
*/
ARGB_FromRGB(A,RGB){
    A := A & 0xFF, RGB := RGB & 0xFFFFFF
    return ((RGB | (A << 24)) & 0xFFFFFFFF)
}


/********************************************
* ARGB(A,RGB)
*
* returns the ARGB value from BGR
*
*********************************************
*/
ARGB_FromBGR(A, BGR){
   return ARGB(A & 0xFF, BGR & 0xFF,(BGR >> 8) & 0xFF, (BGR >> 16) & 0xFF)
}

RGBtoHSV(RGB, byref h, byref s, byref v)
{
	_RGBtoHSV(ToFloat(ARGB_GET(RGB, "R")),  ToFloat(ARGB_GET(RGB, "G")),  ToFloat(ARGB_GET(RGB, "B")), h, s, v)
}

;adapted from http://www.cs.rit.edu/~ncs/color/t_convert.html
;// r,g,b values are from 0 to 1
;// h = [0,360], s = [0,1], v = [0,1]
;//      if s == 0, then h = -1 (undefined)
_RGBtoHSV(r,  g,  b, byref h, byref s, byref v)
{
   ;float min, max, delta
   min := Min(r, g, b)
   max := Max(r, g, b)
   v := max           ;// v
   delta := max - min
   if( max != 0 )
      s := delta / max      ;// s
   else {
      ;// r = g = b = 0      // s = 0, v is undefined
      s := 0
      h := -1
      return
   }
   if(r == max)
      h := (g - b) / delta      ;// between yellow & magenta
   else if(g == max)
      h := 2 + (b - r) / delta   ;// between cyan & yellow
   else
      h := 4 + (r - g) / delta   ;// between magenta & cyan
   h *= 60            ;// degrees
   if(h < 0)
      h += 360
}

Min(x,y,z){
	return x < y && x < z ? x : ( z > y ? y : z )	
}
Max(x,y,z){
	return x > y && x > z ? x : ( z < y ? y : z )	
}
;0-1
ToFloat(item){
   return 1 / 0xFF * item
}
